/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dx.rop.code;

import com.android.dx.rop.type.TypeList;
import com.android.dx.util.Hex;
import com.android.dx.util.IntList;
import com.android.dx.util.LabeledItem;

/**
 * Basic block of register-based instructions.
 */
public final class BasicBlock implements LabeledItem {

	/** {@code >= 0;} target label for this block */
	private final int label;

	/** {@code non-null;} list of instructions in this block */
	private final InsnList insns;

	/**
	 * {@code non-null;} full list of successors that this block may branch to
	 */
	private final IntList successors;

	/**
	 * {@code >= -1;} the primary / standard-flow / "default" successor, or
	 * {@code -1} if this block has no successors (that is, it exits the
	 * function/method)
	 */
	private final int primarySuccessor;

	/**
	 * Constructs an instance. The predecessor set is set to {@code null}.
	 * 
	 * @param label
	 *            {@code >= 0;} target label for this block
	 * @param insns
	 *            {@code non-null;} list of instructions in this block
	 * @param successors
	 *            {@code non-null;} full list of successors that this block may
	 *            branch to
	 * @param primarySuccessor
	 *            {@code >= -1;} the primary / standard-flow / "default"
	 *            successor, or {@code -1} if this block has no successors (that
	 *            is, it exits the function/method or is an unconditional throw)
	 */
	public BasicBlock(int label, InsnList insns, IntList successors,
			int primarySuccessor) {
		if (label < 0) {
			throw new IllegalArgumentException("label < 0");
		}

		try {
			insns.throwIfMutable();
		} catch (NullPointerException ex) {
			// Elucidate exception.
			throw new NullPointerException("insns == null");
		}

		int sz = insns.size();

		if (sz == 0) {
			throw new IllegalArgumentException("insns.size() == 0");
		}

		for (int i = sz - 2; i >= 0; i--) {
			Rop one = insns.get(i).getOpcode();
			if (one.getBranchingness() != Rop.BRANCH_NONE) {
				throw new IllegalArgumentException("insns[" + i + "] is a "
						+ "branch or can throw");
			}
		}

		Insn lastInsn = insns.get(sz - 1);
		if (lastInsn.getOpcode().getBranchingness() == Rop.BRANCH_NONE) {
			throw new IllegalArgumentException("insns does not end with "
					+ "a branch or throwing " + "instruction");
		}

		try {
			successors.throwIfMutable();
		} catch (NullPointerException ex) {
			// Elucidate exception.
			throw new NullPointerException("successors == null");
		}

		if (primarySuccessor < -1) {
			throw new IllegalArgumentException("primarySuccessor < -1");
		}

		if (primarySuccessor >= 0 && !successors.contains(primarySuccessor)) {
			throw new IllegalArgumentException("primarySuccessor "
					+ primarySuccessor + " not in successors " + successors);
		}

		this.label = label;
		this.insns = insns;
		this.successors = successors;
		this.primarySuccessor = primarySuccessor;
	}

	/**
	 * {@inheritDoc} Instances of this class compare by identity. That is,
	 * {@code x.equals(y)} is only true if {@code x == y}.
	 */
	@Override
	public boolean equals(Object other) {
		return (this == other);
	}

	/**
	 * {@inheritDoc} Return the identity hashcode of this instance. This is
	 * proper, since instances of this class compare by identity (see
	 * {@link #equals}).
	 */
	@Override
	public int hashCode() {
		return System.identityHashCode(this);
	}

	/**
	 * Gets the target label of this block.
	 * 
	 * @return {@code >= 0;} the label
	 */
	public int getLabel() {
		return label;
	}

	/**
	 * Gets the list of instructions inside this block.
	 * 
	 * @return {@code non-null;} the instruction list
	 */
	public InsnList getInsns() {
		return insns;
	}

	/**
	 * Gets the list of successors that this block may branch to.
	 * 
	 * @return {@code non-null;} the successors list
	 */
	public IntList getSuccessors() {
		return successors;
	}

	/**
	 * Gets the primary successor of this block.
	 * 
	 * @return {@code >= -1;} the primary successor, or {@code -1} if this block
	 *         has no successors at all
	 */
	public int getPrimarySuccessor() {
		return primarySuccessor;
	}

	/**
	 * Gets the secondary successor of this block. It is only valid to call this
	 * method on blocks that have exactly two successors.
	 * 
	 * @return {@code >= 0;} the secondary successor
	 */
	public int getSecondarySuccessor() {
		if (successors.size() != 2) {
			throw new UnsupportedOperationException(
					"block doesn't have exactly two successors");
		}

		int succ = successors.get(0);
		if (succ == primarySuccessor) {
			succ = successors.get(1);
		}

		return succ;
	}

	/**
	 * Gets the first instruction of this block. This is just a convenient
	 * shorthand for {@code getInsns().get(0)}.
	 * 
	 * @return {@code non-null;} the first instruction
	 */
	public Insn getFirstInsn() {
		return insns.get(0);
	}

	/**
	 * Gets the last instruction of this block. This is just a convenient
	 * shorthand for {@code getInsns().getLast()}.
	 * 
	 * @return {@code non-null;} the last instruction
	 */
	public Insn getLastInsn() {
		return insns.getLast();
	}

	/**
	 * Returns whether this block might throw an exception. This is just a
	 * convenient shorthand for {@code getLastInsn().canThrow()}.
	 * 
	 * @return {@code true} iff this block might throw an exception
	 */
	public boolean canThrow() {
		return insns.getLast().canThrow();
	}

	/**
	 * Returns whether this block has any associated exception handlers. This is
	 * just a shorthand for inspecting the last instruction in the block to see
	 * if it could throw, and if so, whether it in fact has any associated
	 * handlers.
	 * 
	 * @return {@code true} iff this block has any associated exception handlers
	 */
	public boolean hasExceptionHandlers() {
		Insn lastInsn = insns.getLast();
		return lastInsn.getCatches().size() != 0;
	}

	/**
	 * Returns the exception handler types associated with this block, if any.
	 * This is just a shorthand for inspecting the last instruction in the block
	 * to see if it could throw, and if so, grabbing the catch list out of it.
	 * If not, this returns an empty list (not {@code null}).
	 * 
	 * @return {@code non-null;} the exception handler types associated with
	 *         this block
	 */
	public TypeList getExceptionHandlerTypes() {
		Insn lastInsn = insns.getLast();
		return lastInsn.getCatches();
	}

	/**
	 * Returns an instance that is identical to this one, except that the
	 * registers in each instruction are offset by the given amount.
	 * 
	 * @param delta
	 *            the amount to offset register numbers by
	 * @return {@code non-null;} an appropriately-constructed instance
	 */
	public BasicBlock withRegisterOffset(int delta) {
		return new BasicBlock(label, insns.withRegisterOffset(delta),
				successors, primarySuccessor);
	}

	public String toString() {
		return '{' + Hex.u2(label) + '}';
	}

	/**
	 * BasicBlock visitor interface
	 */
	public interface Visitor {

		/**
		 * Visits a basic block
		 * 
		 * @param b
		 *            block visited
		 */
		public void visitBlock(BasicBlock b);
	}
}
